import gosu.Image;
import gosu.Window;
import gosu.Color;

import org.jruby.*;
import org.jruby.runtime.*;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.javasupport.JavaUtil;
import org.jruby.anno.JRubyMethod;
import static org.jruby.RubyNumeric.*;

import java.awt.Graphics2D;
import java.util.ArrayList;


public class ImageProxy extends RubyObject {
  private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
    public IRubyObject allocate(Ruby runtime, RubyClass klass) {
      return new ImageProxy(runtime, klass);
    }
  };

  private Image _image;

  private final Ruby _runtime;

  public static void createImageClass(Ruby runtime) {
    RubyModule mGosu = runtime.getModule("Gosu");
    RubyClass cImage = mGosu.defineClassUnder("Image", runtime.getObject(), ALLOCATOR);

    cImage.defineAnnotatedMethods(ImageProxy.class);
  }


  public ImageProxy(Ruby runtime, RubyClass type) {
    super(runtime, type);
    _runtime = runtime;
  }


  public void setImage(Image image) { _image = image; }


  @JRubyMethod(name = "initialize",
               required = 2,
               optional = 5,
               frame = true,
               visibility = Visibility.PRIVATE)
  public IRubyObject initialize(IRubyObject[] args) {
    int numArgs = Arity.checkArgumentCount(_runtime, args, 2, 7);

    if (numArgs > 3 && numArgs != 7) {
      Arity.raiseArgumentError(_runtime, numArgs, 7, 7);
    }

    Window window = ((WindowProxy) args[0]).getWindow();
    String filename = (String) JavaUtil.convertRubyToJava(args[1], String.class);
    boolean hardBorders = false;
    if (numArgs > 2) hardBorders = args[2].isTrue();

    try {
      if (numArgs > 3) {
        int srcX = fix2int(args[3]);
        int srcY = fix2int(args[4]);
        int srcWidth = fix2int(args[5]);
        int srcHeight = fix2int(args[6]);
        _image = new Image(window, filename, hardBorders, srcX, srcY, srcWidth, srcHeight);
      } else {
        _image = new Image(window, filename, hardBorders);
      }
    } catch (RuntimeException e) {
      throw _runtime.newRuntimeError(e.toString());
    }

    return this;
  }


  @JRubyMethod(name = "draw",
               required = 3,
               optional = 4)
  public IRubyObject draw(IRubyObject[] args) {
    double x = num2dbl(args[0]);
    double y = num2dbl(args[1]);
    double z = num2dbl(args[2]);

    if (args.length == 3) {
      _image.draw(x, y, z);
    } else if (args.length == 4) {
      _image.draw(x, y, z, num2dbl(args[3]), 1.0);
    } else if (args.length == 5) {
      _image.draw(x, y, z, num2dbl(args[3]), num2dbl(args[4]));
    } else {
      Color c = WindowProxy.getColorFrom(args[5]);

      int mode = Image.DEFAULT;
      if (args.length == 7) {
        mode = WindowProxy.getAlphaModeFrom(_runtime, args[6]);
      }
      _image.draw(x, y, z, num2dbl(args[3]), num2dbl(args[4]), c, mode);
    }

    return _runtime.getNil();
  }


  @JRubyMethod(name = "draw_rot",
               required = 4,
               optional = 6)
  public IRubyObject drawRot(IRubyObject[] args) {
    double x = num2dbl(args[0]);
    double y = num2dbl(args[1]);
    double z = num2dbl(args[2]);
    double angle = num2dbl(args[3]);

    switch (args.length) {
      case 4:
        _image.drawRot(x, y, z, angle);
        break;
      case 5:
        _image.drawRot(x, y, z, angle, num2dbl(args[4]), 0.5);
        break;
      case 6:
        _image.drawRot(x, y, z, angle, num2dbl(args[4]), num2dbl(args[5]));
        break;
      case 7:
        _image.drawRot(x, y, z, angle, num2dbl(args[4]), num2dbl(args[5]), num2dbl(args[6]), 1.0);
        break;
      case 8:
        _image.drawRot(x, y, z, angle, num2dbl(args[4]), num2dbl(args[5]), num2dbl(args[6]), num2dbl(args[7]));
        break;
      default: { // 9 or 10
        Color c = WindowProxy.getColorFrom(args[8]);

        int mode = Image.DEFAULT;
        if (args.length == 10) {
          mode = WindowProxy.getAlphaModeFrom(_runtime, args[9]);
        }
        _image.drawRot(x, y, z, angle, num2dbl(args[4]), num2dbl(args[5]), num2dbl(args[6]), num2dbl(args[7]), c, mode);
        break;
      }
    }

    return _runtime.getNil();
  }


  @JRubyMethod(name = "draw_as_quad",
               required = 13,
               optional = 1)
  public IRubyObject drawAsQuad(IRubyObject[] args) {
    _image.drawAsQuad(
                      num2dbl(args[0]), num2dbl(args[1]), WindowProxy.getColorFrom(args[2]),
                      num2dbl(args[3]), num2dbl(args[4]), WindowProxy.getColorFrom(args[5]),
                      num2dbl(args[6]), num2dbl(args[7]), WindowProxy.getColorFrom(args[8]),
                      num2dbl(args[9]), num2dbl(args[10]), WindowProxy.getColorFrom(args[11]),
                      num2dbl(args[12]), (args.length == 14 ? fix2int(args[13]) : Image.DEFAULT)
                     );
    return _runtime.getNil();
  }


  @JRubyMethod(name = "load_tiles",
               required = 5,
               meta = true)
  public static IRubyObject loadTiles(IRubyObject klass, IRubyObject[] args) {
    Window window = ((WindowProxy) args[0]).getWindow();
    String filename = (String) JavaUtil.convertRubyToJava(args[1], String.class);
    int width = fix2int(args[2]);
    int height = fix2int(args[3]);
    boolean hardBorders = args[4].isTrue();

    ArrayList<Image> images = Image.loadTiles(window, filename, width, height, hardBorders);

    ImageProxy[] array = new ImageProxy[images.size()];
    for (int i = 0; i < array.length; i++) {
      array[i] = (ImageProxy) ((RubyClass) klass).allocate();
      array[i].setImage(images.get(i));
    }

    return RubyArray.newArray(klass.getRuntime(), array);
  }


  @JRubyMethod(name = "from_text",
               required = 4,
               optional = 3,
               meta = true)
  public static IRubyObject fromText(IRubyObject klass, IRubyObject[] args) {
    // There should be either 4 or 7 arguments
    if (args.length > 4 && args.length < 7) {
      Arity.raiseArgumentError(klass.getRuntime(), args.length, 7, 7);
    }

    Window window = ((WindowProxy) args[0]).getWindow();
    String text = ((RubyString) args[1]).getUnicodeValue();
    String fontName = args[2].toString();
    int fontHeight = fix2int(args[3]);

    ImageProxy image = (ImageProxy) ((RubyClass) klass).allocate();

    if (args.length == 4) {
      image.setImage(Image.fromText(window, text, fontName, fontHeight));
      return image;
    }

    float lineSpacing = (float) num2dbl(args[4]);
    float maxWidth = (float) num2dbl(args[5]);
    int align;
    String alignText = ((RubySymbol) args[6]).asJavaString();
    if (alignText.equals("justify")) {
      align = Image.JUSTIFY;
    } else if (alignText.equals("left")) {
      align = Image.LEFT;
    } else if (alignText.equals("right")) {
      align = Image.RIGHT;
    } else if (alignText.equals("center")) {
      align = Image.CENTER;
    } else {
      throw klass.getRuntime().newArgumentError("invalid align");
    }

    image.setImage(Image.fromText(window, text, fontName, fontHeight, lineSpacing, maxWidth, align));
    return image;
  }


  @JRubyMethod(name = "width")
  public IRubyObject getWidth() { return _runtime.newFixnum(_image.getWidth()); }


  @JRubyMethod(name = "height")
  public IRubyObject getHeight() { return _runtime.newFixnum(_image.getHeight()); }
}
